from dataclasses import dataclass, field
from enum import Enum
from typing import Any

from httpx import AsyncClient, Timeout, Response as HttpxResponse, ConnectError, ConnectTimeout

from rosa.love_is_blue.types import PAGINATION, SiteException, BaseResponse, StatusMap
from rosa.scarborough_fair import logger
from rosa.utils import REQUEST_CONTEXT


class HttpxMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"


@dataclass
class HTTPTools:
    prefix: str = ""
    verify: bool = True
    backend_base_url: str = 'http://127.0.0.1'
    timeout: Timeout = field(default_factory=lambda: Timeout(10))

    # 与后端交互的工具类，和rosa强绑定处理方法
    async def generate_url_path(self, url_path: str) -> str:
        if url_path.startswith("http"):
            url = url_path
        else:
            url = f"{self.backend_base_url}{self.prefix}{url_path}"
        url = url.replace("//", '/')
        # 上一步会破坏正常的开头，此步骤将开头的http:/替换为正确的http://
        url = url.replace("http:/", "http://")
        return url

    async def get_http_client(self):
        async with AsyncClient(verify=self.verify, timeout=self.timeout) as client:
            yield client

    async def get_response(self, client: AsyncClient, url: str, method: HttpxMethod = HttpxMethod.GET, params: dict = None, body: dict = None) -> HttpxResponse:
        """
        获取合法的响应
        :param client: httpx客户端
        :param url: 请求url
        :param method: 请求方法
        :param params: 请求参数
        :param body: 请求体
        :return: 响应
        """
        if url == '':
            raise ValueError("url不能为空")
        try:
            match method:
                case HttpxMethod.GET:
                    if params is None:
                        params = dict()
                    response = await client.get(url, params=params)
                case HttpxMethod.POST:
                    if body is None:
                        body = dict()
                    response = await client.post(url, json=body)
                case HttpxMethod.PUT:
                    if body is None:
                        body = dict()
                    response = await client.put(url, json=body)
                case HttpxMethod.DELETE:
                    response = await client.delete(url)
                case _:
                    request_id_var = REQUEST_CONTEXT["request_id"]
                    request_id = request_id_var.get() if request_id_var else ''
                    logger.exception(f"[RequestId: {request_id}] 不支持的HTTPX方法: {method}")
                    raise NotImplementedError
            # 处理返回500的情况
            if response.status_code == 500:
                request_id_var = REQUEST_CONTEXT["request_id"]
                request_id = request_id_var.get() if request_id_var else ''
                logger.exception(f"[RequestId: {request_id}] 后端服务异常，错误原因为{response.text}")
                response = BaseResponse(
                    code=StatusMap.REMOTE_INTERNAL_ERROR.code,
                    success=StatusMap.REMOTE_INTERNAL_ERROR.success,
                    message="后端服务异常，请稍后再试"
                )
                raise SiteException(status_code=StatusMap.REMOTE_INTERNAL_ERROR.http_code, response=response) from None
        except ConnectError as error:
            request_id_var = REQUEST_CONTEXT["request_id"]
            request_id = request_id_var.get() if request_id_var else ''
            logger.exception(f"[RequestId: {request_id}] 后端连接失败，错误原因为{error}")
            response = BaseResponse(
                code=StatusMap.REMOTE_SERVICE_UNAVAILABLE.code,
                success=StatusMap.REMOTE_SERVICE_UNAVAILABLE.success,
                message="后端接口通信失败，请稍后再试"
            )
            raise SiteException(status_code=StatusMap.REMOTE_SERVICE_UNAVAILABLE.http_code, response=response) from None
        except ConnectTimeout as error:
            request_id_var = REQUEST_CONTEXT["request_id"]
            request_id = request_id_var.get() if request_id_var else ''
            logger.exception(f"[RequestId: {request_id}] 后端连接超时，错误原因为{error}")
            response = BaseResponse(
                code=StatusMap.REMOTE_REQUEST_TIMEOUT.code,
                success=StatusMap.REMOTE_REQUEST_TIMEOUT.success,
                message="连接超时，内部通讯受阻，请稍后再试"
            )
            raise SiteException(status_code=StatusMap.REMOTE_REQUEST_TIMEOUT.http_code, response=response) from None
        return response

    async def get_response_data(
            self,
            name: str = 'item',
            url: str = '',
            method: HttpxMethod = HttpxMethod.GET,
            params: dict = None,
            body: dict = None,
            operation: str = '获取所有',
            operation_id: str | None = None,
            original: bool = False
    ) -> dict:
        if url == '':
            raise ValueError("url不能为空")

        request_id_var = REQUEST_CONTEXT["request_id"]
        request_id = request_id_var.get() if request_id_var else ''
        error_message = ""
        try:
            async for client in self.get_http_client():
                response = await self.get_response(client, url, method, params, body)
                error_message = response.text

                logger.debug(f"[RequestId: {request_id}] op:{operation} item:{name} 成功，返回结果为{response.url}-{response.status_code}-{response.content}")
                response_data = response.json()

                if original:
                    return response_data

                success = response_data.get("success") or False
                if not success:
                    logger.error(f"[RequestId: {request_id}] op:{operation} item:{name} 失败，返回结果为{response_data}, 状态码为{response.status_code}")
                    message = f'数据错误，资源未正确获取[{operation_id}:{request_id}] -> {response_data.get("message", "未知错误")}'
                    error_code = response_data.get("code") or StatusMap.REMOTE_REQUEST_FAILED.code
                    response_obj = BaseResponse(code=error_code, success=False, message=message)
                    if 200 <= response.status_code < 300:
                        raise SiteException(status_code=response.status_code, response=response_obj) from None
                    raise SiteException(status_code=StatusMap.REMOTE_REQUEST_FAILED.http_code, response=response_obj) from None

                data = response_data.get("data") or dict()
                return data
        except SiteException as error:
            raise error
        except Exception as error:
            logger.error(f"[RequestId: {request_id}] op:{operation} item:{name} 失败，返回结果为{error_message}，错误原因为{error}")
            message = f'未正确获取资源信息，请稍后再试[{operation_id}:{request_id}] {error_message}'
            response = BaseResponse(
                code=StatusMap.REMOTE_REQUEST_FAILED.code,
                success=StatusMap.REMOTE_REQUEST_FAILED.success,
                message=message
            )

            raise SiteException(status_code=StatusMap.REMOTE_REQUEST_FAILED.http_code, response=response) from None

    async def get_all_handler(
            self,
            name: str,
            url_path: str,
            pagination: PAGINATION | None = None,
            filters: dict[str, str | list[str]] | None = None,
            orders: list[str] | None = None,
            ids: list[int] | None = None,
            original: bool = False
    ) -> tuple[list, int, PAGINATION]:
        url = await self.generate_url_path(url_path)

        params = dict()
        if pagination:
            params["index"] = pagination.index
            params["limit"] = pagination.limit

        if filters:
            params["filters"] = []
            for key in filters:
                value = filters[key]
                if isinstance(value, list):
                    for v in value:
                        params["filters"].append(f"{key}={v}")
                else:
                    params["filters"].append(f"{key}={value}")

        if orders:
            params["orders"] = orders

        if ids:
            params["ids"] = ids

        data = await self.get_response_data(name, url, params=params, operation="获取所有", original=original)
        items = data["items"]
        pagination = PAGINATION(**data["pagination"])

        if pagination.total == 0 and len(items) > 0:
            count = len(items)
        else:
            count = pagination.total

        return items, count, pagination

    async def create_handler(self, name: str, url_path: str, model_dict: dict[str, Any], original: bool = False) -> dict[str, Any]:
        url = await self.generate_url_path(url_path)
        data = await self.get_response_data(name, url, method=HttpxMethod.POST, body=model_dict, operation="创建", original=original)
        return data

    async def update_handler(self, name: str, url_path: str, model_dict: dict[str, Any], original: bool = False) -> dict[str, Any]:
        url = await self.generate_url_path(url_path)
        data = await self.get_response_data(name, url, method=HttpxMethod.PUT, body=model_dict, operation="更新", original=original)
        return data

    async def delete_handler(self, name: str, url_path: str, original: bool = False) -> dict[str, Any]:
        url = await self.generate_url_path(url_path)
        data = await self.get_response_data(name, url, method=HttpxMethod.DELETE, operation="删除", original=original)
        return data

    async def get_one_handler(self, name: str, url_path: str, original: bool = False) -> dict[str, Any]:
        url = await self.generate_url_path(url_path)
        data = await self.get_response_data(name, url, operation="获取单个", original=original)
        return data
